﻿using AntMovement.Comm.Model;
using AntMovement.Core.LabelService;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace AntMovement.Comm
{
    public class TagManager : ITagManager
    {
        private ITagParsing _parsing;
        public ITagParsing Parsing { get => _parsing; }
        private Dictionary<long, VariableModel> _cache = new Dictionary<long, VariableModel>();
        public TagManager()
        {
            _parsing = new TagParsing(new Func<long, bool>(x => _cache.ContainsKey(x)), x =>
            {
                if (!_cache.ContainsKey(x.Hash))
                {
                    _cache.Add(x.Hash, x);
                }
            });
        }

        public byte[] GetValue(string tag)
        {
            long hash = tag.GetHashCode();
            VariableModel model = _cache.FirstOrDefault(x => x.Key == hash).Value;
            if (model != null)
            {
                List<byte> bytes = new List<byte>();
                bytes.Add(TypeToByte(model.DataType));
                if (model.IsClass && !model.IsArray)
                {
                    int classSize = (int)StructToBytes.GetClassSize(model.Source);
                    byte[] buff = new byte[classSize];
                    int boolSize = 0;
                    StructToBytes.ToBytes(model.Source, buff, ref boolSize, 0);
                    bytes.AddRange(buff);
                }
                if (model.IsClass && model.IsArray)
                {
                    Array array = (Array)model.Property.GetValue(model.Source, null);
                    for (int i = 0; i < array.Length; i++)
                    {
                        Type typeSub = array.GetValue(i).GetType();
                        if (typeSub.IsClass)
                        {
                            object obj = array.GetValue(i);
                            int classSize = (int)StructToBytes.GetClassSize(obj);
                            byte[] buff = new byte[classSize];
                            int boolSize = 0;
                            StructToBytes.ToBytes(obj, buff, ref boolSize, 0);
                            bytes.AddRange(buff);
                        }
                        else
                        {
                            bytes.AddRange(ToBytes(array.GetValue(i), model.DataType));
                        }
                    }
                }
                if (!model.IsClass && model.IsArray)
                {
                    Array arraySig = model.Source as Array;
                    bytes.AddRange(ToBytes(arraySig.GetValue(model.Index), model.DataType));
                }
                if (!model.IsClass && !model.IsArray)
                {
                    bytes.AddRange(ToBytes(model.GetValue(), model.DataType));
                }
                return bytes.ToArray();
            }
            throw new NotImplementedException("The label does not exist.");
        }

        private byte TypeToByte(string datatype)
        {
            switch (datatype)
            {
                case "Int32": return 0xC4;
                case "UInt32": return 0xC8;
                case "Single": return 0xCA;
                case "Int64": return 0xC5;
                case "UInt64": return 0xC9;
                case "Double": return 0xCB;
                case "Int16": return 0xC3;
                case "UInt16": return 0xC7;
                case "Byte": return 0xD1;
                case "Boolean": return 0xC1;
                case "String": return 0xD0;
                case "class": return 0xA0;
            }
            return 0xFF;
        }

        private byte[] ToBytes(object value, string dataType)
        {
            List<byte> bytes = new List<byte>();
            int bytes_length = 0;
            switch (dataType)
            {
                case "Int32":
                case "UInt32":
                case "Single": bytes_length = 4; break;
                case "Int64":
                case "UInt64":
                case "Double": bytes_length = 8; break;
                case "Int16":
                case "UInt16": bytes_length = 2; break;
                case "Byte":
                case "Boolean": bytes_length = 1; break;
                case "String":
                    bytes_length = value.ToString().Length;
                    break;
            }
            byte[] value_bytes = default;
            if (value.GetType().Name == "String")
            {
                value_bytes = new byte[bytes_length + 2];
                value_bytes[0] = (byte)(bytes_length);
                value_bytes[1] = (byte)(bytes_length >> 8);
                var ascii = Encoding.ASCII.GetBytes(value.ToString());
                Array.Copy(ascii, 0, value_bytes, 2, ascii.Length);
            }
            else
            {
                value_bytes = new byte[bytes_length];
                StructToBytes.GetBytes(value, value_bytes, 0);
            }
            bytes.AddRange(value_bytes);
            return bytes.ToArray();
        }

        public byte[] GetValues(string[] tags)
        {
            List<byte> bytes = new List<byte>();
            for (int i = 0; i < tags.Length; i++)
            {
                bytes.AddRange(GetValue(tags[i]));
            }
            return bytes.ToArray();
        }

        public bool SetValue(string tag, object value)
        {
            long hash = tag.GetHashCode();
            VariableModel model = _cache.FirstOrDefault(x => x.Key == hash).Value;
            if (model != null)
            {
                if (model.IsClass && !model.IsArray)
                {
                    byte[] bytes = value as byte[];
                    int count = 0;
                    StructToBytes.FromBytes(model.Source, bytes, ref count, 0);
                }
                if (model.IsClass && model.IsArray)
                {
                    Array array = (Array)model.Property.GetValue(model.Source, null);
                    double length = 0;
                    int count = 0;
                    for (int i = 0; i < array.Length; i++)
                    {
                        Type typeSub = array.GetValue(i).GetType();
                        if (typeSub.IsClass)
                        {
                            byte[] bytes = value as byte[];
                            object obj = array.GetValue(i);
                            length = StructToBytes.FromBytes(obj, bytes, ref count, length);
                        }
                        else
                        {
                            Array sourcArray = value as Array;
                            array.SetValue(sourcArray.GetValue(i), i);
                        }
                    }
                }
                if (!model.IsClass && model.IsArray)
                {
                    Array array = model.Source as Array;
                    array.SetValue(value, model.Index);
                }
                if (!model.IsClass && !model.IsArray)
                {
                    model.SetValue(value);
                }
                return true;
            }
            throw new NotImplementedException("The label does not exist.");
        }

        public bool SetValues(string[] tags, object[] values)
        {
            if (tags.Length != values.Length)
            {
                throw new ArgumentException("The label array length is not equal to the value array length...");
            }
            for (int i = 0; i < tags.Length; i++)
            {
                if (!SetValue(tags[i], values[i]))
                {
                    throw new ArgumentException("Value setting failure...");
                }
            }
            return true;
        }
    }
}
